﻿namespace Snoop.Infrastructure.Diagnostics.Providers
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Markup;
    using Snoop.Data.Tree;

    public class UnresolvedDynamicResourceDiagnosticProvider : DiagnosticProvider
    {
        private static readonly Type? resourceReferenceExpressionType = typeof(ResourceReferenceExpressionConverter).Assembly.GetType("System.Windows.ResourceReferenceExpression");

        private static readonly FieldInfo? resourceKeyFieldInfo = resourceReferenceExpressionType?.GetField("_resourceKey", BindingFlags.NonPublic | BindingFlags.Instance);

        public override string Name => "Unresolved {DynamicResource} usages";

        public override string Description => "Searches for {DynamicResource} usages that could not be resolved.";

        protected override IEnumerable<DiagnosticItem> GetDiagnosticItemsInternal(TreeItem treeItem)
        {
            if (treeItem.Target is not FrameworkElement frameworkElement)
            {
                yield break;
            }

            if (resourceReferenceExpressionType is null
                || resourceKeyFieldInfo is null)
            {
                yield break;
            }

            foreach (PropertyDescriptor? property in TypeDescriptor.GetProperties(frameworkElement.GetType()))
            {
                if (property is null)
                {
                    continue;
                }

                var dpd = DependencyPropertyDescriptor.FromProperty(property);
                if (dpd is null)
                {
                    continue;
                }

                var localValue = frameworkElement.ReadLocalValue(dpd.DependencyProperty);
                // ReSharper disable once ConditionIsAlwaysTrueOrFalse
                if (localValue is null)
                {
                    continue;
                }

                // ResourceReferenceExpression is internal...
                var localValueType = localValue.GetType();

                if (resourceReferenceExpressionType.IsAssignableFrom(localValueType))
                {
                    var resourceKey = resourceKeyFieldInfo.GetValue(localValue);

                    if (resourceKey is null)
                    {
                        continue;
                    }

                    var resource = frameworkElement.TryFindResource(resourceKey);
                    if (resource is null)
                    {
                        yield return
                            new(this,
                                "Resource not resolved",
                                $"The resource '{resourceKey}' on property '{dpd.DisplayName}' could not be resolved.",
                                DiagnosticArea.Resource,
                                DiagnosticLevel.Error)
                            {
                                TreeItem = treeItem,
                                Dispatcher = frameworkElement.Dispatcher,
                                SourceObject = frameworkElement
                            };
                    }
                }
            }
        }
    }
}